package org.streets.wicket;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.wicket.Application;
import org.apache.wicket.protocol.http.IWebApplicationFactory;
import org.apache.wicket.protocol.http.WicketFilter;
import org.apache.wicket.util.listener.IChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.streets.commons.util.ResourceUtils;
import org.streets.context.HiveContext;
import org.streets.context.HiveInitializer;
import org.streets.context.internal.HiveContextImpl;
import org.streets.wicket.internal.ReloadingClassLoader;

/**
 * HiveWicketFilter is the main setting on web.xml to use wicket on streets
 * 
 * @author dzb
 *
 */
public class HiveWicketFilter extends WicketFilter implements HiveWicketSettings {
	
    private static final Logger logger = LoggerFactory.getLogger(HiveWicketFilter.class);

    private FilterConfig filterConfig;
    private String applicationName;
    private String applicationMode;
    private String basePackage;
    private String[] moduleNames;
    private ReloadingClassLoader reloadingClassLoader;
    private HiveContext context;
    private String filterPattern;
        
    private static final ThreadLocal<HiveContext> CURRENT_CONTEXT = new ThreadLocal<HiveContext>();

    public String getFilterPattern() {
		return filterPattern;
	}
    
	@Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
    {
        //HttpContext.set((HttpServletRequest) request, (HttpServletResponse) response);
		HiveContext previous = CURRENT_CONTEXT.get();
		try {
			CURRENT_CONTEXT.set(context);
	    	super.doFilter(request, response, chain);
		} finally {
			CURRENT_CONTEXT.set(previous);
		}
        //HttpContext.clear();
    }
	/**
     * Overridden in subclasses to provide additional module definitions beyond those normally located. This
     * implementation returns an empty array.
     */
    protected Class<?>[] provideExtraModules(ServletContext context) 
    {    	
    	return new Class<?>[0];    	
    }    
    
    
    public HiveContext getHiveContext()
    {
    	return context;
    }
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException
    {
    	    	
        this.filterConfig = filterConfig;
        
        this.applicationName = filterConfig.getFilterName();
        
        this.basePackage = moduleBasePackage();
        
        this.moduleNames = moduleNames(); 
        
        this.applicationMode = applicationMode();
        
        logger.info("Starting STREETS v2 POWERED Application '{}' in '{}' mode", applicationName, applicationMode);
        
        String root = filterConfig.getServletContext().getRealPath("/");
        
        context = new HiveContextImpl(applicationName, root);
        context.registerModules(provideExtraModules(filterConfig.getServletContext()));

        // Register default module
        if (basePackage == null) basePackage = "";
        
    	for (String moduleName : moduleNames) {
    		String builderName = null;
    		boolean isFullName = moduleName.indexOf(".") > 0 ;
    		if (isFullName) {
    			builderName = moduleName;
    		} else {
    			builderName = basePackage.length() == 0 ? (moduleName+".Module") : (basePackage+"."+moduleName+".Module");
    		}
    		Class<?> builder = null;
    		try {
    			builder = Class.forName(builderName);
    		} catch (ClassNotFoundException ex) {
    			logger.error("Module builder: ["+builderName+"] not exist.");
    			builder = null;
    		}
    		if (builder != null) {    			
    			context.registerModules(builder);
    		}
    	}                
    	// Startup HiveContext
        context.startup();
        
        CURRENT_CONTEXT.set(context);
        // Call HiveInitializer service
        HiveInitializer ai = context.registry().service("HiveInitializer",  HiveInitializer.class);
        //ai.initialize(filterConfig.getServletContext(), context);
        ai.initialize(context);
        // print all services in context
        context.announce();        
        //调用重载初始化
        init(context); 
                        
        initReloadingClassloader(filterConfig);
        // Initialize wicket filter 
        super.init(filterConfig);
        
        filterPattern = getFilterPath(null);                        
    }
    
    /**
     * Shuts down and discards the registry.  Invokes {@link #destroy(org.apache.tapestry5.ioc.Registry)} to allow
     * subclasses to peform any shutdown logic, then shuts down the registry, and removes it from the ServletContext.
     */
    public final void destroy()  {
        destroy(context);
        context.shutdown();
        CURRENT_CONTEXT.remove();
        context = null;
        filterConfig = null;
    }
    /**
     * Invoked from {@link #init(FilterConfig)} after the Registry has been created, to allow any additional
     * initialization to occur. This implementation does nothing, and my be overriden in subclasses.
     *
     * @param context A started context from which services may be extracted
     * @throws ServletException
     */
    protected void init(HiveContext context) throws ServletException   {
    	// Invoke for inherited calbacks
    }

    /**
     * Invoked from {@link #destroy()} to allow subclasses to add additional shutdown logic to the filter. The Registry
     * will be shutdown after this call. This implementation does nothing, and may be overridden in subclasses.
     *
     * @param context A started context
     */
    protected void destroy(HiveContext context)  {
        // Invoke for inherited callbacks
    }

    protected void initReloadingClassloader(final FilterConfig filterConfig) {
    	
        String reload = initParameter(APPLICATION_RELOADING);
        
        if (reload != null) {
            String packageName = (ResourceUtils.packageExist(reload) ? reload : basePackage);
            logger.info("Starting reloading for '{}' on package '{}'", applicationName, packageName);
            final String pattern = packageName + ".*";
            if (reloadingClassLoader == null) {
                reloadingClassLoader = new ReloadingClassLoader(getClass().getClassLoader(), pattern);
            }
            reloadingClassLoader.setListener(new IChangeListener() {
                public void onChange() {
                    reloadingClassLoader.destroy();
                    reloadingClassLoader = new ReloadingClassLoader(getClass().getClassLoader(), pattern);
                    try
                    {
                        init(filterConfig);
                    }
                    catch (ServletException e)
                    {
                        throw new RuntimeException(e.getMessage(), e);
                    }
                }
            });
        }
    }

    @Override
    public ClassLoader getClassLoader()
    {
        return reloadingClassLoader == null ? super.getClassLoader() : reloadingClassLoader;
    }


    public String basePackage()
    {
        return basePackage;
    }


    public String applicationName()
    {
        return applicationName;
    }

	public String applicationClass() {
		
		String applicationClass = filterConfig.getInitParameter(APPLICATION_CLASS);
		if (applicationClass == null) {
			applicationClass = filterConfig.getInitParameter("applicationClassName");
		}
		return applicationClass;
	}
	
    public String applicationMode()
    {
        String mode = filterConfig.getInitParameter(APPLICATION_MODE);
        if (mode == null) {
        	mode = initParameter("wicket." + Application.CONFIGURATION);
        }
        return mode == null ? Application.DEVELOPMENT : mode;
    }

	public String moduleBasePackage() {
        String name = filterConfig.getInitParameter(APPLICATION_MODULE_BASE_PACKAGE);
        return name == null ? "" : name.endsWith(".") ? name.substring(0, name.length() - 1) : name;
	}

	public String[] moduleNames() {
		String names = filterConfig.getInitParameter(APPLICATION_MODULE_NAMES);
		String[] result = names == null ? new String[]{""} : names.split("\\s*,\\s*");
		return result;
	}
	

    public String initParameter(String name)
    {
        return filterConfig.getInitParameter(name);
    }


    @Override
    protected IWebApplicationFactory getApplicationFactory()
    {
        String factory = initParameter(APP_FACT_PARAM);
        if (factory == null) {
        	factory = "org.streets.wicket.HiveWebApplicationFactory";
        }
        IWebApplicationFactory appFactory = null;
        try {
        	Class<?> clazz = Class.forName(factory);
        	appFactory = (IWebApplicationFactory) clazz.newInstance();
        }
        catch(ClassNotFoundException e)
        {
            throw new IllegalArgumentException(String.format("Cannot instanciate '%s' class not found", factory), e);
        }
        catch(Exception e)
        {
            throw new IllegalArgumentException(String.format("Cannot instanciate class '%s' to IWebApplicationFactory", factory), e);
        }
        return appFactory;
    }

    public String locate(String res)
    {
        res = res.startsWith("/") ? res.substring(1) : res;
        res = res.endsWith("/") ? res.substring(0, res.length() - 1) : res;
        String base_package = basePackage();
        return res.length() == 0 ? base_package : base_package + (base_package.length() == 0 ? "" : ".") + res.replaceAll("/", ".");
    }

}
